package com.legendframework.core.cmd;

import com.legendframework.core.aop.utils.AopUtils;
import com.legendframework.core.cmd.annotation.Command;
import com.legendframework.core.cmd.annotation.*;
import com.legendframework.core.cmd.exception.ParameterException;
import com.legendframework.core.AbstractLegendPlugin;
import com.legendframework.core.dao.page.Page;
import com.legendframework.core.dao.page.PageUtil;
import com.legendframework.core.dao.support.ClassUtils;
import com.legendframework.core.ioc.IParameter;
import com.legendframework.core.support.lang3.StringUtils;
import org.bukkit.command.CommandException;
import org.bukkit.command.CommandSender;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.entity.Player;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * @Auther: 196
 * @Description: 抽象Controller父类, 继承该类表示为一个Controller
 */
public abstract class BaseCommand implements ICommand , CommandHelp {

    private AbstractLegendPlugin plugin;

    public void setPlugin(AbstractLegendPlugin plugin) {
        this.plugin = plugin;
    }

    protected AbstractLegendPlugin getPlugin() {
        return plugin;
    }

    @Override
    public void successHandle(CommandSender sender, Method method, Object... params) {
        if (
                AopUtils.getTargetClass(this).isAnnotationPresent(CmdSuccessHandle.class) ||
                        AopUtils.getTargetClass(this).isAnnotationPresent(CmdHandle.class) ||
                        method.isAnnotationPresent(CmdSuccessHandle.class) ||
                        method.isAnnotationPresent(CmdHandle.class)) {
            sender.sendMessage("执行成功");
        }
    }

    @Override
    public void failHandle(CommandSender sender, Throwable e, Method method) {
        e.printStackTrace();
        if (
                AopUtils.getTargetClass(this).isAnnotationPresent(CmdFailHandle.class) ||
                        AopUtils.getTargetClass(this).isAnnotationPresent(CmdHandle.class) ||
                        method.isAnnotationPresent(CmdFailHandle.class) ||
                        method.isAnnotationPresent(CmdHandle.class)) {
            if (e instanceof CommandException) {
                sender.sendMessage("执行失败,原因：" + e.getMessage());
            } else {
                e.printStackTrace();
                sender.sendMessage("执行失败");
            }
        }
    }

    /**
     * 当指令参数的个数正确，但与方法所匹配的类型不一致时，会调用该处理器
     *
     * 例如：参数类型为int类型， 玩家输入的这个参数为 "abc" ，则会调用该处理器
     *
     * @param sender 指令发送者
     * @param e 异常信息
     * @param method 封装入参失败的指令方法
     * @param parameter 导致参数错误的这个参数，它可能是一个入参 {@link Parameter} 也可能是一个字段 {@link Field}
     * @param param 导致错误方法实参
     * @param index 参数的索引
     */
    public void paramsErrorHandle(CommandSender sender, Throwable e, Method method, IParameter parameter, Object param, int index) {
        CmdParam cmdParam = parameter.getAnnotation(CmdParam.class);
        if (
                AopUtils.getTargetClass(this).isAnnotationPresent(CmdFailHandle.class) ||
                        AopUtils.getTargetClass(this).isAnnotationPresent(CmdHandle.class) ||
                        method.isAnnotationPresent(CmdFailHandle.class) ||
                        method.isAnnotationPresent(CmdHandle.class)) {
            if (e instanceof ParameterException){
                sender.sendMessage("参数错误，参数位置： " + (index + 1) + " , 参数名称: " + cmdParam.title() + " , 参数值： " + String.valueOf(param) + " , 预期：" + parameter.getType().getName() + " , 实际: " + param.getClass().getName() + " , 错误原因： "+e.getMessage());
            } else {
                e.printStackTrace();
                sender.sendMessage("参数错误，参数位置： " + (index + 1) + " , 参数名称: " + cmdParam.title() + " , 参数值： " + String.valueOf(param) + " , 预期：" + parameter.getType().getName() + " , 实际: " + param.getClass().getName());
            }
        }
    }

    @Override
    public int helpItemNum() {
        return 7;
    }

    /**
     * 获取@Command注解
     *
     * @return
     */
    protected Command getThisCommandAnnotation() {
        return AopUtils.getTargetClass(this).getAnnotation(Command.class);
    }

    /**
     * 为子类提供的help公用方法
     *
     * @param page 页码
     * {@link Cmd} 中的指令前会拼接子类 {@link Command#cmd()} 参数
     * <p>
     * 例如有两个子类 分别为：PlayerCmd ，ServerCmd
     * 其类上的 {@link Command#cmd()} 参数分别为 'p' , 's'
     * 那么这两个子类的help指令的调用为：
     * 1. PlayerCmd : '/根指令 p help' or '/根指令 p'
     * 2. ServerCmd : '/根指令 s help' or '/根指令 s'
     * 执行的help指令会进入到不同的子类对象的该方法 help() 中 ， 子类可以复写这个方法
     */
    @Cmd({"help", ""})
    @CmdFailHandle
    public void help(
            CommandSender sender,
            org.bukkit.command.Command command,
            @CmdParam(title = "页码", desc = "数字", demo = "1", required = false) Integer page
    ) {
        page = page == null ? 1 : page;
        Command thisCommand = getThisCommandAnnotation();
        List<CmdHelp> cmdHelps = getHelps(sender);
        try {
            CmdHelp help = createCmdhelp(
                    BaseCommand.class.getMethod("help", CommandSender.class, org.bukkit.command.Command.class, Integer.class),
                    thisCommand,
                    this
            );
            //分页且渲染
            renderingHelp(sender, command, help, PageUtil.page(cmdHelps, page, helpItemNum()));
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        }
    }

    /**
     * 校验这个方法是否是 {@link BaseCommand#help(CommandSender, org.bukkit.command.Command, Integer)}
     * @param m 方法
     * @return
     */
    protected boolean isHelpMethod(Method m){
        return StringUtils.equals("help", m.getName()) &&
                m.getParameters().length == 3 &&
                m.getParameterTypes()[0].equals(CommandSender.class) &&
                m.getParameterTypes()[1].equals(org.bukkit.command.Command.class) &&
                m.getParameterTypes()[2].equals(Integer.class) &&
                m.isAnnotationPresent(Cmd.class);
    }

    /**
     * 默认渲染风格
     *
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    @Override
    public void renderingHelp(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage) {
        if (cmdHelpPage.getTotalSum() == 0) {
            sender.sendMessage("当前注册的指令: [" + command.getName() + "] , 没有绑定任何子指令");
            return;
        } else if (cmdHelpPage.getList().size() == 0) {
            sender.sendMessage("当前页没有任何数据");
        }
        renderingHelpPrefix(sender, command, help, cmdHelpPage);
        renderingHelpBody(sender, command, help, cmdHelpPage);
        renderingHelpSuffix(sender, command, help, cmdHelpPage);
    }

    /**
     * 渲染指令help前缀
     *
     * @param sender      指令执行者
     * @param command     指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpPrefix(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage) {
        Command annotation = getThisCommandAnnotation();
        sender.sendMessage("=================" + annotation.title() + "指令帮助 " + cmdHelpPage.getCurrentPage() + "/" + cmdHelpPage.getTotalPage() + "===============");
    }

    /**
     * 展示指令帮助列表
     * <p>
     * 无需关注分页逻辑，只需要自定渲染风格
     *
     * @param sender      指令执行者
     * @param command     指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpBody(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage) {
        cmdHelpPage.getList().forEach(cmdHelp -> {
            StringBuilder param = new StringBuilder();
            for (CmdParam cmdParam : cmdHelp.getCmdParams()) {
                param.append(" <").append(cmdParam.title()).append(">");
            }
            String[] cmds = Arrays.stream(cmdHelp.getCmd())
                    .map(cmd -> ("/" + command.getName() + " " + cmd + param.toString()).replace("  ", " ")).toArray(String[]::new);
            sender.sendMessage(
                    cmdHelp.getCmdName() +
                            " >> " +
                            StringUtils.join(cmds, " 或 ")
            );
        });
    }

    /**
     * 渲染指令help后缀
     *
     * @param sender      指令执行者
     * @param command     指令
     * @param help        当前准备渲染的指令封装类
     * @param cmdHelpPage 需要渲染的指令帮助列表
     */
    protected void renderingHelpSuffix(CommandSender sender, org.bukkit.command.Command command, CmdHelp help, Page<CmdHelp> cmdHelpPage) {
    }

    /**
     * 获取当前指令类的所以除help()外的指令
     *
     * @return
     */
    protected List<CmdHelp> getThisCmds() {
        Command command = getThisCommandAnnotation();
        return ClassUtils.getMethodsByClass(AopUtils.getTargetClass(this))
                .stream()
                .filter(method -> method.isAnnotationPresent(Cmd.class) &&
                        !(StringUtils.equals("help", method.getName()) &&
                                method.getParameters().length == 3 &&
                                method.getParameterTypes()[0].equals(CommandSender.class) &&
                                method.getParameterTypes()[1].equals(org.bukkit.command.Command.class) &&
                                method.getParameterTypes()[2].equals(Integer.class)
                        ))
                .map(method -> createCmdhelp(method, command, this)).collect(Collectors.toList());
    }

    /**
     * 根据当前实现类提供的子指令与类上注解的是否跟路径
     * 来决定需要返回的所有指令菜单
     *
     * @return
     */
    protected List<CmdHelp> getHelps(CommandSender sender) {
        List<CmdHelp> cmdHelps = getSubCommands()
                .stream()
                .map(impl -> {
                    Command command = AopUtils.getTargetClass(impl).getAnnotation(Command.class);
                    List<Method> help = ClassUtils.getMethodsByClass(AopUtils.getTargetClass(impl))
                            .stream()
                            .filter(this::isHelpMethod)
                            .collect(Collectors.toList());
                    if (help.size() == 0) {
                        return null;
                    }
                    return createCmdhelp(help.get(0), command, impl);
                }).filter(Objects::nonNull)
                .collect(Collectors.toList());
        cmdHelps.addAll(getThisCmds());
        //筛选出执行者能够执行的指令
        cmdHelps = cmdHelps.stream().filter(item -> checkSendUseCmd(sender, item)).collect(Collectors.toList());
        return cmdHelps;
    }

    /**
     * 获取当前类的所有子指令
     * 只包含一层级的儿子
     * @return
     */
    protected List<ICommand> getSubCommands(){
        return getPlugin().getStore().getCommandManagerStore().getCommands(getPlugin()).stream()
                .filter(impl -> !AopUtils.getTargetClass(impl).equals(AopUtils.getTargetClass(this)))
                .filter(impl -> AopUtils.getTargetClass(impl).isAnnotationPresent(CommandParentNode.class))
                .filter(impl -> Arrays.stream(AopUtils.getTargetClass(impl).getAnnotation(CommandParentNode.class).value())
                        .anyMatch(cls -> cls == AopUtils.getTargetClass(this)))
                .collect(Collectors.toList());
    }

    /**
     * 构建指令封装对象
     * <p>
     * 用于指令help输出
     *
     * @param method       指令方法
     * @param classCommand 指令方法所属类的注解
     * @param commandBean  指令方法所属的类
     * @return
     */
    protected CmdHelp createCmdhelp(Method method, Command classCommand, ICommand commandBean) {
        String helpTitle = StringUtils.isEmpty(method.getAnnotation(Cmd.class).title()) ?
                (StringUtils.isEmpty(classCommand.title()) ? AopUtils.getTargetClass(commandBean).getName() : classCommand.title())
                :
                method.getAnnotation(Cmd.class).title();
        CmdHelp cmdHelp = new CmdHelp();
        cmdHelp.setCmdMethod(method);
        cmdHelp.setCmdName(helpTitle);
        cmdHelp.setCmd(Arrays.stream(method.getAnnotation(Cmd.class).value()).map(s -> StringUtils.isNotEmpty(classCommand.cmd()) ? (classCommand.cmd() + " " + s) : s).toArray(String[]::new));
        cmdHelp.setCmdParams(Arrays.stream(method.getParameters())
                .filter(parameter -> parameter.isAnnotationPresent(CmdParam.class))
                .map(parameter -> parameter.getAnnotation(CmdParam.class))
                .toArray(CmdParam[]::new));
        cmdHelp.setPermissionStr(CommandManager.getPermission(method.getAnnotation(Permission.class),plugin.getRootCmd(),classCommand,method.getAnnotation(Cmd.class)));
        return cmdHelp;
    }

    /**
     * 校验这个执行者是否可以使用这个指令
     * @param sender 执行者
     * @param cmdHelp 指令
     * @return
     */
    protected boolean checkSendUseCmd(CommandSender sender , CmdHelp cmdHelp){
        if (cmdHelp.getCmdMethod().isAnnotationPresent(OnlyOp.class)) {
            if (!sender.isOp()) {
                return false;
            }
        }
        //筛选出执行者可用的指令
        if (sender instanceof Player){
            if (cmdHelp.getCmdMethod().isAnnotationPresent(OnlyServer.class)){
                return false;
            }
            return !cmdHelp.getCmdMethod().isAnnotationPresent(Permission.class) || sender.hasPermission(cmdHelp.getPermissionStr());
        }else if (sender instanceof ConsoleCommandSender){
            return !cmdHelp.getCmdMethod().isAnnotationPresent(OnlyPlayer.class);
        }
        return false;
    }
}
